#include <MonitorDriver.h>
#include <Kernel.h>
#include <Asm.h>
#include <ProcessManager.h>
#include <SerialDriver.h>

MonitorDriver::MonitorDriver()
{
  memoryLocation = (u16int *)0xB8000;
  cursorX = 0;
  cursorY = 0;
  intMode = dec;
}

void MonitorDriver::init()
{
  // Initialise the resource and functions.
  resource = new Resource((Callable*)this, "kerr");
  resource->add((Resource::Call1)&MonitorDriver::write1, MonitorDriver_IF::writeString);
  resource->add((Resource::Call1)&MonitorDriver::writeHex1, MonitorDriver_IF::writeHex);
  resource->add((Resource::Call1)&MonitorDriver::writeDec1, MonitorDriver_IF::writeDec);
  resource->add((Resource::Call1)&MonitorDriver::put1, MonitorDriver_IF::put);
  resource->add((Resource::Call0)&MonitorDriver::clear, MonitorDriver_IF::clear);
  resource->add((Resource::Call3)&MonitorDriver::writeRaw, MonitorDriver_IF::writeRaw);
}

MonitorDriver::~MonitorDriver()
{
  cursorX = 0;
  cursorY = 0;
}

//
// write -- Outputs the string c to the monitor, with foreground colour
//          foreColour and background backColour.
//
void MonitorDriver::write(char *c, Colour foreColour, Colour backColour)
{

  int i = 0;
  while (c[i])
  {
    put(c[i++], foreColour, backColour);
  }

}

//
// writeHex -- Outputs the given signed 32-bit integer to the monitor
//             as a hexidecimal number, prefixed with "0x", with the
//             foreground/background colours specified.
//
void MonitorDriver::writeHex(s32int n, Colour foreColour, Colour backColour)
{

  s32int tmp;

  write("0x", foreColour, backColour);

  bool noZeroes = true;

  int i;
  for (i = 28; i > 0; i -= 4)
  {
    tmp = (n >> i) & 0xF;
    if (tmp == 0 && noZeroes)
    {
      continue;
    }
    
    if (tmp >= 0xA)
    {
      noZeroes = false;
      put (tmp-0xA+'a', foreColour, backColour);
    }
    else
    {
      noZeroes = false;
      put( tmp+'0', foreColour, backColour);
    }
  }
  
  tmp = n & 0xF;
  if (tmp >= 0xA)
  {
    put (tmp-0xA+'a', foreColour, backColour);
  }
  else
  {
    put( tmp+'0', foreColour, backColour);
  }
  
}

//
// writeDec -- Outputs the given signed 32-bit integer to the monitor
//             as a decimal number, with the
//             foreground/background colours specified.
//
void MonitorDriver::writeDec(s32int n, Colour foreColour, Colour backColour)
{
  if (n == 0)
  {
    put('0', foreColour, backColour);
    return;
  }
  
  s32int acc = n;
  char c[32];
  int i = 0;
  while (acc > 0)
  {
    c[i] = '0' + acc%10;
    acc /= 10;
    i++;
  }
  c[i] = 0;

  char c2[32];
  c2[i--] = 0;
  int j = 0;
  while(i >= 0)
  {
    c2[i--] = c[j++];
  }
  write(c2, foreColour, backColour);
  
}

//
// put -- Outputs the character c to the monitor, with foreground colour
//        foreColour and background backColour.
//
void MonitorDriver::put(char c, Colour foreColour, Colour backColour)
{
//   getLock();

  u8int  attributeByte = (backColour << 4) | (foreColour & 0x0F);
  u16int attribute = attributeByte << 8;
  u16int *location;
  
  // Handle a backspace, by moving the cursor back one space
  if (c == 0x08 && cursorX)
  {
    cursorX--;
  }

  // Handle a tab by increasing the cursor's X, but only to a point
  // where it is divisible by 8.
  else if (c == 0x09)
  {
    cursorX = (cursorX+8) & ~(8-1);
  }
  
  // Handle carriage return
  else if (c == '\r')
  {
    cursorX = 0;
  }

  // Handle newline by moving cursor back to left and increasing the row
  else if (c == '\n')
  {
    cursorX = 0;
    cursorY++;
  }
  
  else if(c >= ' ')
  {
    location = memoryLocation + (cursorY*80 + cursorX);
    *location = c | attribute;
    cursorX++;
  }

  // Check if we need to insert a new line because we have reached the end 
  // of the screen.
  if (cursorX >= 80)
  {
    cursorX = 0;
    cursorY ++;
  }
  
  // Scroll the screen if needed
  scroll();
  // Move the cursor
  moveCursor();

  releaseLock();
}

//
// clear -- Clears the screen.
//
void MonitorDriver::clear()
{
  getLock();
  
  // Make an attribute byte for the default colours
  u8int attributeByte = (black << 4) | (white & 0x0F);
  u16int blank = 0x20 /* space */ | (attributeByte << 8);
  
  for (int i = 0; i < 80*25; i++)
  {
    memoryLocation[i] = blank;
  }
  
  // Move the cursor
  cursorX = 0;
  cursorY = 0;
  moveCursor();
  
  releaseLock();
}

//
// scroll -- Scrolls the screen up a row if nessecary.
//
void MonitorDriver::scroll()
{

  // Get a space character with the default colour attributes.
  u8int attributeByte = (black << 4) | (white & 0x0F);
  u16int blank = 0x20 /* space */ | (attributeByte << 8);

  // Row 25 is the end, this means we need to scroll up
  if(cursorY >= 25)
  {
      // Move the current text chunk that makes up the screen
      // back in the buffer by a line 
      
      for (int i = 0*80; i < 24*80; i++)
      {
        memoryLocation[i] = memoryLocation[i+80];
      }

      for (int i = 24*80; i < 25*80; i++)
      {
        memoryLocation[i] = blank;
      }
      cursorY = 24;
  }
}

//
// moveCursor -- Moves the hardware cursor.
//
void MonitorDriver::moveCursor()
{
  u16int temp = cursorY * 80 + cursorX;
  outb(0x3D4, 14);
  outb(0x3D5, temp >> 8);
  outb(0x3D4, 15);
  outb(0x3D5, temp);
}

//
// operator<< -- Nice syntax for printing strings
//
MonitorDriver &MonitorDriver::operator<<(char *c)
{
  write(c);
  return *this;
}

//
// operator<< -- Nice syntax for printing strings
//
MonitorDriver &MonitorDriver::operator<<(const char *c)
{
  write(c);
  return *this;
}

//
// operator<< -- Nice syntax for printing hex integers
//
MonitorDriver &MonitorDriver::operator<<(u32int i)
{

  if (intMode == dec)
  {
    writeDec(i);
  }
  else
  {
    writeHex(i);
  }
  return *this;
}

static void changeKernel(u32int *ptr, u32int newVal)
{
  *ptr = newVal;
}

//
// operator<< -- Nice syntax for printing hex integers
//
MonitorDriver &MonitorDriver::operator<<(Special s)
{
  switch(s)
  {
  case endl:
    put('\n');
    break;
  case dec:
    changeKernel((u32int*)&intMode, dec);
    break;
  case hex:
    changeKernel((u32int*)&intMode, hex);
    break;
  }
  return *this;
}

void MonitorDriver::writeRaw(u16int *ptr, u32int x, u32int y)
{
  Kernel::memcpy((u8int*)memoryLocation, (u8int*)ptr, sizeof(u16int)*80*25);
  cursorX = x;
  cursorY = y;
  moveCursor();
}
